#include <jni.h>
#include <fcntl.h>
#include "SignalHandler.h"
#include "JNIBridge.h"
#include "CrashAnalyser.h"
#include "dlopen.h"
#include "inlineHook.h"
#include "IOCanary.h"
#include "xhook/xhook.h"
#include "xhook/xh_core.h"

//
// Created by Amon on 2021/10/16.
//

void *get_contented_monitor;
void *get_lock_owner_thread_id;
void *thread_create_callback;

void *(*old_create_callback)(void *) = nullptr;

#define THREAD_RUN_INFO_LENGTH 512

extern "C"
JNIEXPORT void JNICALL
Java_com_amon_canary_NativeCrashMonitor_nativeInit(JNIEnv *env, jobject thiz,
                                                   jobject callback) {
    if (callback != nullptr) {
        callback = env->NewGlobalRef(callback);
        JavaVM *javaVm;
        env->GetJavaVM(&javaVm);
        jclass nativeCrashMonitorClazz = env->GetObjectClass(thiz);
        nativeCrashMonitorClazz = static_cast<jclass>(env->NewGlobalRef(nativeCrashMonitorClazz));
        auto *pJNIBridge = new JNIBridge(javaVm, callback, nativeCrashMonitorClazz);
        init_condition();
        pthread_t tid;
        int ret = pthread_create(&tid, nullptr, thread_crash_monitor, pJNIBridge);
        if (ret) {
            LOGE("pthread_create error, ret:%d", ret);
        }
    }
}

extern "C"
JNIEXPORT void JNICALL
Java_com_amon_canary_NativeCrashMonitor_nativeSetup(JNIEnv *env, jobject thiz) {
    installSignalHandlers();
    installAlternateStack();
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_amon_canary_NativeThreadMonitor_getContentThreadId(JNIEnv *env, jclass clazz,
                                                            jlong thread_native_address) {
    if (get_contented_monitor == nullptr && get_lock_owner_thread_id == nullptr) {
        return -1;
    }
    int monitorObj = ((int (*)(long)) get_contented_monitor)(thread_native_address);
    if (monitorObj == 0) {
        return 0;
    }
    int monitorThreadId = ((int (*)(int)) get_lock_owner_thread_id)(monitorObj);
    return monitorThreadId;
}

const char *get_lock_owner_symbol_name(int level) {
    if (level < 29) {
        return "_ZN3art7Monitor20GetLockOwnerThreadIdEPNS_6mirror6ObjectE";
    } else {
        return "_ZN3art7Monitor20GetLockOwnerThreadIdENS_6ObjPtrINS_6mirror6ObjectEEE";
    }
}

#define SYM_GET_CONTENDED_MONITOR "_ZN3art7Monitor19GetContendedMonitorEPNS_6ThreadE"
#define SYM_THREAD_CREATE_CALLBACK  "_ZN3art6Thread14CreateCallbackEPv"

extern "C"
JNIEXPORT jint JNICALL
Java_com_amon_canary_NativeThreadMonitor_nativeInit(JNIEnv *env, jclass clazz,
                                                    jint sdk_version) {
    ndk_init(env);

    void *so_addr = ndk_dlopen("libart.so", RTLD_LAZY);
    if (so_addr == nullptr) {
        LOGD("ndk_dlopen libart.so failed");
        return -1;
    }

    //GetContendedMonitor: 获取当前线程在竞争的锁,可以用 nm命令解析so
    get_contented_monitor = ndk_dlsym(so_addr, SYM_GET_CONTENDED_MONITOR);
    if (get_contented_monitor == nullptr) {
        LOGD("ndk_dlsym SYM_GET_CONTENDED_MONITOR failed");
        return -2;
    }

    // GetLockOwnerThreadId：当前锁被哪个线程 id 持有了，只有这个方法可以间接的做到
    get_lock_owner_thread_id = ndk_dlsym(so_addr, get_lock_owner_symbol_name(sdk_version));
    if (get_lock_owner_thread_id == nullptr) {
        LOGD("ndk_dlsym get_lock_owner_symbol_name failed");
        return -3;
    }

    thread_create_callback = ndk_dlsym(so_addr, SYM_THREAD_CREATE_CALLBACK);
    if (thread_create_callback == nullptr) {
        LOGD("ndk_dlsym SYM_THREAD_CREATE_CALLBACK failed");
        return -4;
    }
    return 0;
}

const char *getThreadRunInfo() {
    char *path = (char *) calloc(1, PATH_MAX);
    char *thread_info = (char *) calloc(1, THREAD_RUN_INFO_LENGTH);
    snprintf(path, PATH_MAX, "/proc/%d/task/%d/stat", getpid(), gettid());
    LOGE("path:%s", path);
    FILE *commFile = fopen(path, "r");
    if (commFile) {
        fgets(thread_info, THREAD_RUN_INFO_LENGTH, commFile);
        fclose(commFile);
    }
    if (thread_info) {
        int length = strlen(thread_info);
        if (thread_info[length - 1] == '\n') {
            thread_info[length - 1] = '\0';
        }
    }
    free(path);
    return thread_info;
}

void *new_create_callback(void *arg) {
    long startTime = time(nullptr);
    void *result = old_create_callback(arg);
    long alive_time = time(nullptr) - startTime;
    LOGE("线程执行完毕，存活时间:%ldS", alive_time);
    const char *thread_info = getThreadRunInfo();
    LOGE("线程proc task info:%s", thread_info);
    return result;
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_amon_canary_NativeThreadMonitor_monitorThread(JNIEnv *env, jclass clazz) {
    if (registerInlineHook((uint32_t) thread_create_callback, (uint32_t) new_create_callback,
                           (uint32_t **) &old_create_callback) != ELE7EN_OK) {
        return -1;
    }
    if (inlineHook((uint32_t) thread_create_callback) != ELE7EN_OK) {
        return -2;
    }
    return 0;
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_amon_canary_NativeThreadMonitor_getCurrentThreadId(JNIEnv *env, jclass clazz,
                                                            jlong thread_native_address,
                                                            jint sdk_int) {
    if (thread_native_address != 0) {
        if (sdk_int > 20) {
            int *pInt = reinterpret_cast<int *>(thread_native_address);
            pInt += 3;
            return *pInt;
        }
    }
    return 0;
}

using namespace iocanary;
IoCanary *ioCanary;

bool isMainThread() {
    return getpid() == gettid();
}

int64_t getTickCount() {
    struct timeval tv{};
    gettimeofday(&tv, nullptr);
    return (int64_t) tv.tv_sec * 1000000 + (int64_t) tv.tv_usec;
}

static int (*original_open64)(const char *path, int flags, mode_t mode);

int xhook_open(const char *path, int flags, mode_t mode) {
    LOGE("监听到文件打开:%s", path);
    if (!isMainThread()) {
        return open(path, flags, mode);
    }
    int fd = open(path, flags, mode);
    ioCanary->OnOpen(fd, path);
    return fd;
}

int xhook_open64(const char *path, int flags, mode_t mode) {
    LOGE("监听到文件打开64:%s", path);
    if (!isMainThread()) {
        return open(path, flags, mode);
    }
    int fd = original_open64(path, flags, mode);
    ioCanary->OnOpen(fd, path);
    return fd;
}

int xhook_read(int fd, void *buf, size_t count) {
    LOGE("监听到文件读:%d size", fd);
    if (!isMainThread()) {
        return read(fd, buf, count);
    }

    int64_t start = getTickCount();
    ssize_t size = read(fd, buf, count);
    int64_t cost_time = getTickCount() - start;
    LOGE("监听到文件读:%d size:%d", fd, size);
    ioCanary->OnRead(fd, size, cost_time);
    return size;
}

ssize_t xhook_write(int fd, const void *buf, size_t count) {
    if (!isMainThread()) {
        return write(fd, buf, count);
    }
    int64_t start = getTickCount();
    ssize_t size = write(fd, buf, count);
    int64_t cost_time = getTickCount() - start;
    LOGE("监听到文件写:%d size:%d", fd, size);
    ioCanary->OnWrite(fd, size, cost_time);
    return size;
}

int xhook_close(int fd) {
    if (!isMainThread()) {
        return close(fd);
    }
    int ret = close(fd);
    LOGE("监听到文件关闭:%d size:%d", fd, ret);
    ioCanary->OnClose(fd);
    return ret;
}

static int (*original_android_fdsan_close_with_tag)(int fd, uint64_t expected_tag);

const char *read_link(int fd) {
    char path[PATH_MAX] = {};
    char fileName[FILENAME_MAX] = {};
    sprintf(path, "/proc/%d/fd/%d", getpid(), fd);
    readlink(path, fileName, FILENAME_MAX);
    return fileName;
}

int xhook_android_fdsan_close_with_tag(int fd, uint64_t expected_tag) {
    const char *fileName = read_link(fd);
    int ret = original_android_fdsan_close_with_tag(fd, expected_tag);
    LOGE("hook_android_fdsan_close_with_tag %d fileName=%s", fd, fileName);
    return ret;
}

extern "C" JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *javaVm, void *reserved) {
    JNIEnv *env;
    if (javaVm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    init(env);
    return JNI_VERSION_1_6;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_amon_canary_IOCanary_hook(JNIEnv *env, jclass clazz) {
    ioCanary = new IoCanary();

    xh_core_enable_debug(ANDROID_LOG_DEBUG);

    xhook_register("libopenjdkjvm.so", "open", (void *) (xhook_open), nullptr);
    xhook_register("libjavacore.so", "open", (void *) (xhook_open), nullptr);
    xhook_register("libopenjdk.so", "open", (void *) (xhook_open), nullptr);

    xhook_register("libopenjdkjvm.so", "open64", (void *) (xhook_open64),
                   (void **) &original_open64);
    xhook_register("libjavacore.so", "open64", (void *) (xhook_open64), (void **) &original_open64);
    xhook_register("libopenjdk.so", "open64", (void *) (xhook_open64), (void **) &original_open64);

    xhook_register("libopenjdkjvm.so", "read", (void *) (xhook_read), nullptr);
    xhook_register("libjavacore.so", "read", (void *) (xhook_read), nullptr);
    xhook_register("libopenjdk.so", "read", (void *) (xhook_read), nullptr);

    xhook_register("libopenjdkjvm.so", "write", (void *) (xhook_write), nullptr);
    xhook_register("libjavacore.so", "write", (void *) (xhook_write), nullptr);
    xhook_register("libopenjdk.so", "write", (void *) (xhook_write), nullptr);

    xhook_register("libopenjdkjvm.so", "close", (void *) (xhook_close), nullptr);
    xhook_register("libjavacore.so", "close", (void *) (xhook_close), nullptr);
    xhook_register("libopenjdk.so", "close", (void *) (xhook_close), nullptr);

    xhook_register("libjavacore.so", "android_fdsan_close_with_tag",
                   (void *) xhook_android_fdsan_close_with_tag,
                   (void **) &original_android_fdsan_close_with_tag);

    xhook_refresh(1);
}